import { any, has, must } from "../core/logic";
import { Vertex } from "../gpu/types/Vertex";
import { ParamManager } from "../gpu/ParamManager";
import { Texture } from "../gpu/types/Texture";
import { Buffer } from "../gpu/types/Buffer";
/**
 * 材质类。
 * @class
 * @memberof w.geometry
 */
class Geometry extends ParamManager {
  /**
   * 创建材质
   * @constructor
   * @param {*} opts
   * @param {string} opts.name 【可选】名称。
   * @param {Array} opts.position 顶点位置信息
   * @param {Array} opts.indices 顶点索引信息
   * @param {Array} opts.color 顶点颜色信息
   * @param {Number} opts.colorStep 顶点颜色长度
   * @param {Array} opts.normal 顶点法线信息
   * @param {Array} opts.tangent 顶点切线信息
   * @param {Array} opts.texCood0 顶点纹理坐标0信息
   * @param {Array} opts.texCood1 顶点纹理坐标1信息
   */
  constructor(opts = {}, share) {
    super(opts, share);
    this._cameraView = any(opts.cameraView, "all"); //相机的视图,缺省使用vp矩阵。
    this.geometryType = "Geometry";
    this._hash = "";
    this.position = opts.position;
    this.indices = opts.indices;
    this.color = opts.color;
    this.colorStep = opts.colorStep;
    this.normal = opts.normal;
    this.tangent = opts.tangent;
    this.texCood0 = opts.texCood0;
    this.texCood1 = opts.texCood1;
    this.morphOffset = opts.morphOffset;
    this.morphWeightCount = opts.morphWeightCount;
    this.morphWeight = opts.morphWeight;
    this.morphPositionOffset = opts.morphPositionOffset;
    if (opts.autoUpdate === undefined) {
      this.autoUpdate = true;
    } else {
      this.autoUpdate = opts.autoUpdate;
    }
    this.update();
  }
  get hash() {
    return this._hash;
  }
  get cameraView() {
    return this._cameraView;
  }

  get geometryType() {
    return this._geometryType;
  }

  set geometryType(v) {
    this._geometryType = v;
    this.addConfigParam("geometryType", v);
    this.update();
    return this;
  }

  set position(v) {
    if (!has(v)) {
      return;
    }
    this._position = v;
    this.addParam("position");
    if (this.has("position")) {
      this.initVertex(this.position, "position", 3);
    }
    this.update();
    return this;
  }

  get position() {
    return this._position;
  }

  set indices(v) {
    if (!has(v)) {
      return;
    }
    this._indices = v;
    this.addParam("indices");
    if (this.has("indices")) {
      this.initVertex(this.indices, "indices", 1);
    }
    this.update();
    return this;
  }

  get indices() {
    return this._indices;
  }

  set color(v) {
    if (!has(v)) {
      return;
    }
    this._color = v;
    this.addParam("color");
    if (v.constructor.name === "Float32Array") {
      this.addConfigParam("colorF32", v.constructor.name);
    }
    if (this.colorStep === 3) {
      this.addConfigParam("colorVec3", true);
    }
    if (this.has("color")) {
      if (this.colorStep === 3) {
        this.initVertex(this.color, "color", 3);
      } else {
        this.initVertex(this.color, "color", 4);
      }
    }
    this.update();
    return this;
  }

  get color() {
    return this._color;
  }

  set normal(v) {
    if (!has(v)) {
      return;
    }
    this._normal = v;
    this.addParam("normal");
    this.update();
    if (this.has("normal")) {
      this.initVertex(this.normal, "normal", 3);
    }
    return this;
  }

  get normal() {
    return this._normal;
  }

  set tangent(v) {
    if (!has(v)) {
      return;
    }
    this._tangent = v;
    this.addParam("tangent");
    if (this.has("tangent")) {
      this.initVertex(this.tangent, "tangent", 4);
    }
    this.update();
    return this;
  }

  get tangent() {
    return this._tangent;
  }

  set texCood0(v) {
    if (!has(v)) {
      return;
    }
    this._texCood0 = v;
    this.addParam("texCood0");
    if (this.has("texCood0")) {
      this.initVertex(this.texCood0, "texCood0", 2);
    }
    this.update();
    return this;
  }

  get texCood0() {
    return this._texCood0;
  }

  set texCood1(v) {
    if (!has(v)) {
      return;
    }
    this._texCood1 = v;
    this.addParam("texCood1");
    if (this.has("texCood1")) {
      this.initVertex(this.texCood1, "texCood1", 2);
    }
    this.update();
    return this;
  }

  get texCood1() {
    return this._texCood1;
  }
  set joints0(v) {
    if (!has(v)) {
      return;
    }
    this._joints0 = v;
    this.addParam("joints0");
    if (this.has("joints0")) {
      this.initVertex(this.joints0, "joints0", v.step);
    }
    this.update();
    return this;
  }

  get joints0() {
    return this._joints0;
  }
  set weights0(v) {
    if (!has(v)) {
      return;
    }
    this._weights0 = v;
    this.addParam("weights0");
    if (this.has("weights0")) {
      this.initVertex(this.weights0, "weights0", v.step);
    }
    this.update();
    return this;
  }

  get weights0() {
    return this._weights0;
  }
  set joints1(v) {
    if (!has(v)) {
      return;
    }
    this._joints1 = v;
    this.addParam("joints1");
    if (this.has("joints1")) {
      this.initVertex(this.joints1, "joints1", v.step);
    }
    this.update();
    return this;
  }

  get joints1() {
    return this._joints1;
  }
  set weights1(v) {
    if (!has(v)) {
      return;
    }
    this._weights1 = v;
    this.addParam("weights1");
    if (this.has("weights1")) {
      this.initVertex(this.weights1, "weights1", v.step);
    }
    this.update();
    return this;
  }

  get weights1() {
    return this._weights1;
  }

  get morphOffset() {
    return this._morphOffset;
  }

  set morphOffset(v) {
    if (!has(v)) {
      return;
    }
    this._morphOffset = v;
    let morphOffsetParameter = this.getParam("u_MorphTargetsSampler");
    if (!morphOffsetParameter) {
      morphOffsetParameter = new Texture({
        id: "u_MorphTargetsSampler",
        name: "u_MorphTargetsSampler",
        usage:
          GPUTextureUsage.TEXTURE_BINDING |
          GPUTextureUsage.COPY_DST |
          GPUTextureUsage.COPY_SRC,
        visibility: GPUShaderStage.VERTEX,
        sampleType: "float",
        format: "rgba32float",
        dimension: "2d-array",
        multisampled: false,
        data: v,
      });
      this.addParam("u_MorphTargetsSampler", morphOffsetParameter);
    } else {
      if (!morphOffsetParameter.state) {
        morphOffsetParameter.buffer(v);
      }
    }
    morphOffsetParameter.state = true;
    this.update();
    return this;
  }

  get morphWeightCount() {
    return this._morphWeightCount;
  }

  set morphWeightCount(v) {
    if (!has(v)) {
      return;
    }
    this._morphWeightCount = v;
    this.addConfigParam("WEIGHT_COUNT", v);
    this.update();
    return this;
  }
  get morphPositionOffset() {
    return this._morphPositionOffset;
  }

  set morphPositionOffset(v) {
    if (!has(v)) {
      return;
    }
    this._morphPositionOffset = v;
    this.addConfigParam("morphPositionOffset", v);
    this.update();
    return this;
  }
  get morphNormalOffset() {
    return this._morphNormalOffset;
  }

  set morphNormalOffset(v) {
    if (!has(v)) {
      return;
    }
    this._morphNormalOffset = v;
    this.addConfigParam("morphNormalOffset", v);
    this.update();
    return this;
  }
  get morphTangentOffset() {
    return this._morphNormalOffset;
  }

  set morphTangentOffset(v) {
    if (!has(v)) {
      return;
    }
    this._morphTangentOffset = v;
    this.addConfigParam("morphTangentOffset", v);
    this.update();
    return this;
  }
  get morphWeight() {
    return this._morphWeight;
  }
  set morphWeight(v) {
    if (!has(v)) {
      return;
    }
    this._morphWeight = v;
    let data = new Float32Array(v);
    let morphWeightParameter = this.getParam("u_morphWeights");
    if (!morphWeightParameter) {
      morphWeightParameter = new Buffer({
        name: "u_morphWeights",
        usage:
          GPUBufferUsage.STORAGE |
          GPUBufferUsage.COPY_DST |
          GPUBufferUsage.COPY_SRC,
        visibility: GPUShaderStage.VERTEX,
        type: "read-only-storage",
        size: data.byteLength,
        data: data,
      });
      this.addParam("u_morphWeights", morphWeightParameter);
    } else {
      // if (!morphWeightParameter.state) {
      morphWeightParameter.buffer(data);
      // }
    }
    morphWeightParameter.state = true;
    this.update();
    return this;
  }
  get jointMatrixs() {
    return this._jointMatrixs;
  }
  set jointMatrixs(v) {
    if (!has(v)) {
      return;
    }
    this._jointMatrixs = v;
    let jointMatrixsParameter = this.getParam("jointMatrixs");
    if (!jointMatrixsParameter) {
      jointMatrixsParameter = new Texture({
        id: "jointMatrixs",
        name: "jointMatrixs",
        usage:
          GPUTextureUsage.TEXTURE_BINDING |
          GPUTextureUsage.COPY_DST |
          GPUTextureUsage.COPY_SRC,
        visibility: GPUShaderStage.VERTEX,
        sampleType: "float",
        format: "rgba32float",
        dimension: "2d",
        multisampled: false,
        data: v,
      });
      this.addParam("jointMatrixs", jointMatrixsParameter);
    } else {
      if (!jointMatrixsParameter.state) {
        jointMatrixsParameter.buffer(v);
      }
    }
    jointMatrixsParameter.state = true;
    this.update();
    return this;
  }
  initVertex(data, name, itemLength, stepMode) {
    let id = `${name}`;
    let param = this.getParam(id);
    if (param && param.size != data.byteLength) {
      param.destroy();
      this.removeParam(id);
    }
    param = this.getParam(id);
    if (!param) {
      if (data instanceof GPUBuffer) {
        param = new Vertex({
          name,
          buffer: data,
        });
        this.addParam(id, param);
        param.state = true;
      } else {
        param = new Vertex({
          name,
          usage:
            name === "indices"
              ? GPUBufferUsage.INDEX |
                GPUBufferUsage.COPY_DST |
                GPUBufferUsage.VERTEX
              : GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
          size: data.byteLength,
          itemLength,
          data,
          stepMode: stepMode,
        });
        this.addParam(id, param);
        param.state = true;
      }
    } else {
      param.buffer(data);
      param.state = true;
    }
  }
  bufferToGPU(data, name, visibility = GPUShaderStage.FRAGMENT) {
    let parameter = this.getParam(name);
    if (parameter) {
      if (!parameter.state) {
        parameter.buffer(data);
      }
    } else {
      parameter = new Buffer({
        name: name,
        usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
        visibility,
        type: "uniform",
        size: data.byteLength,
        data,
      });
      this.addParam(name, parameter);
    }
    parameter.state = true;
  }
  updateState() {
    //全部为同步状态，状态为true
    this.setState(true);
  }
  updateHash() {
    let hash = "";
    this.getAll().forEach(([paramsId, param]) => {
      if (param && param.isConfig) {
        hash =
          hash +
          paramsId +
          (typeof param.value === "object"
            ? JSON.stringify(param.value)
            : param.value);
      } else {
        hash = hash + paramsId;
      }
    });
    this._hash = hash;
  }
  update() {
    if (!this.autoUpdate) {
      //如果父类中未更新完成，或自动更新关闭，并且不强制更新。
      return;
    }
    //if(!this.position) return 修改为参数的判断逻辑
    // if(!this.position) return
    if (!this.has("position") && !this.has("buffer") && !this.has("particle"))
      return;
    if (!this.getState()) {
      this.updateState();
    }
    if (!this.getState()) {
      return;
    }
    let oldhash = this.hash;
    this.updateHash();
    const layoutFlag = oldhash != this.hash;
    if (layoutFlag) {
      this.setState(layoutFlag, "layout");
    }
    this.notice();
  }
  toGPU() {
    // 生成bindGroup和groupLayout
    if (this.getState("layout")) {
      const params = this.getAllParams();
      // console.log(params);
      this.defs = {};
      this.config = {};
      this.vertexBuffer = [];
      this.geometryParams = [];
      this.geometryEntriesGroup = [];
      this.geometrEntriesLayout = [];
      for (let index = 0; index < params.length; index++) {
        const param = params[index];
        this.defs[param.name] = param.value;
        if (param.group === 5) {
          this.config[param.name] = param.value;
        } else if (param.group === 4) {
          this.geometryParams.push(param);
          if (param.name != "indices") {
            this.vertexBuffer.push(param.gpu.layout);
          }
        } else if (param.group === 1) {
          this.geometryEntriesGroup.push(param.gpu.entry);
          this.geometrEntriesLayout.push(param.gpu.layout);
        }
      }
      this.setState(false, "layout");
    }
    this._position = undefined;
    this._indices = undefined;
    this._normal = undefined;
    this._texCood0 = undefined;
    this._texCood1 = undefined;
  }
  destroy() {
    if (this.watchers.length <= 0) {
      this._position = undefined;
      this._indices = undefined;
      this._normal = undefined;
      this._texCood0 = undefined;
      this._texCood1 = undefined;
      this.removeAllParam();
      return true;
    }
    return false;
  }
}

export { Geometry };
